package com.itcz.maker.template;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.itcz.maker.meta.Meta;
import com.itcz.maker.meta.enums.FileGeneratorTypeEnum;
import com.itcz.maker.meta.enums.FileTypeEnum;
import com.itcz.maker.template.model.TemplateMakerConfig;
import com.itcz.maker.template.model.TemplateMakerFileConfig;
import com.itcz.maker.template.model.TemplateMakerModelConfig;
import com.itcz.maker.template.model.TemplateMakerOutputConfig;

import java.io.File;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 模板文件制作器
 */
public class TemplateMaker {

    /**
     * 使用封装参数调用制作模板的方法
     *
     * @param templateMakerConfig
     * @return
     */
    public static Long makeTemplate(TemplateMakerConfig templateMakerConfig) {
        Meta meta = templateMakerConfig.getMeta();
        String originProjectPath = templateMakerConfig.getOriginProjectPath();
        TemplateMakerFileConfig fileConfig = templateMakerConfig.getFileConfig();
        TemplateMakerModelConfig modelConfig = templateMakerConfig.getModelConfig();
        TemplateMakerOutputConfig outputConfig = templateMakerConfig.getOutputConfig();
        Long id = templateMakerConfig.getId();

        // 调用制作模板的方法
        return makeTemplateByOriginProjectPath(meta, originProjectPath, fileConfig, modelConfig, outputConfig, id);
    }

    /**
     * 根据原始项目路径制作模板文件（用于本地调用），生成模板文件和meta.json文件
     *
     * @param newMeta 元数据对象
     * @param originProjectPath 原始项目路径
     * @param templateMakerFileConfig 模板制作文件配置
     * @param templateMakerModelConfig 模板制作模型配置
     * @param templateMakerOutputConfig 模板制作输出配置
     * @param id id
     * @return
     */
    public static Long makeTemplateByOriginProjectPath(Meta newMeta, String originProjectPath,
                                    TemplateMakerFileConfig templateMakerFileConfig,
                                    TemplateMakerModelConfig templateMakerModelConfig,
                                    TemplateMakerOutputConfig templateMakerOutputConfig,
                                    Long id) {
        // 没有id则生成id
        if(id == null) {
            id = IdUtil.getSnowflakeNextId();
        }

        // 项目根目录
        String projectPath = System.getProperty("user.dir");
        // 复制目录
        String tempDirPath = projectPath + File.separator + ".temp";
        String templatePath = tempDirPath + File.separator + id;
        // 该目录不存在时进行创建多级目录，并将原资源目录复制到该目录下
        if(!FileUtil.exist(templatePath)) {
            FileUtil.mkdir(templatePath);
            FileUtil.copy(originProjectPath, templatePath, true);
        }

        return makeTemplateByTemplatePath(newMeta, templatePath, templateMakerFileConfig, templateMakerModelConfig, templateMakerOutputConfig, id);

    }

    /**
     * 根据模板地址制作模板文件（用于web调用），生成模板文件和meta.json文件
     *
     * @param newMeta 元数据对象
     * @param templatePath 模板文件地址
     * @param templateMakerFileConfig 模板制作文件配置
     * @param templateMakerModelConfig 模板制作模型配置
     * @param templateMakerOutputConfig 模板制作输出配置
     * @param id id
     * @return
     */
    public static Long makeTemplateByTemplatePath(Meta newMeta, String templatePath,
                                    TemplateMakerFileConfig templateMakerFileConfig,
                                    TemplateMakerModelConfig templateMakerModelConfig,
                                    TemplateMakerOutputConfig templateMakerOutputConfig,
                                    Long id) {

        // 一、输入信息，获取到项目根目录（使用hutool工具包获取所有遍历深度为1的文件和文件夹，
        // 转换成流处理，排除非文件夹的干扰，并且只获取第一个文件夹，为空则报异常，最后返回项目根目录的绝对路径）
        String sourceRootPath = FileUtil.loopFiles(new File(templatePath), 1, null)
                .stream()
                .filter(File::isDirectory)
                .findFirst()
                .orElseThrow(RuntimeException::new)
                .getAbsolutePath();
        // Windows系统下需要对路径进行转义
        sourceRootPath = sourceRootPath.replaceAll("\\\\", "/");

        // 二、制作文件模板
        List<Meta.FileConfig.FileInfo> newFileInfoList = makeFileTemplates(templateMakerFileConfig, templateMakerModelConfig, sourceRootPath);
        // 处理模型信息
        List<Meta.ModelConfig.ModelInfo> newModelInfoList = getModelInfoList(templateMakerModelConfig);

        // 三、生成配置文件
        // 将meta.json文件输出到工作目录的目录下（原因：测试模板制作器中的bug4）
        String metaOutputPath = templatePath + File.separator + "meta.json";
        // 如若已有meta文件，说明不是第一次制作，则在meta文件基础上进行改造
        if(FileUtil.exist(metaOutputPath)) {
            // 1.将meta文件中的信息转换成Mate对象中的属性信息
            Meta oldMeta = JSONUtil.toBean(FileUtil.readUtf8String(metaOutputPath), Meta.class);
            // 通过 BeanUtil.copyProperties 复制新对象的属性到老对象
            // （如果属性为空则不复制,主要是生成器中的基本信息可能会在再次制作时发生变化）
            BeanUtil.copyProperties(newMeta, oldMeta, CopyOptions.create().ignoreNullValue());
            // 将老对象复制给新对象
            newMeta = oldMeta;

            // 2.追加文件配置参数
            List<Meta.FileConfig.FileInfo> fileInfoList = newMeta.getFileConfig().getFiles();
            fileInfoList.addAll(newFileInfoList);

            // 3.追加模型配置参数
            List<Meta.ModelConfig.ModelInfo> modelInfoList = newMeta.getModelConfig().getModels();
            modelInfoList.addAll(newModelInfoList);

            // 配置去重
            newMeta.getFileConfig().setFiles(distinctFiles(fileInfoList));
            newMeta.getModelConfig().setModels(distinctModels(modelInfoList));
        } else {
            // 配置文件参数
            Meta.FileConfig fileConfig = new Meta.FileConfig();
            fileConfig.setSourceRootPath(sourceRootPath);
            List<Meta.FileConfig.FileInfo> fileInfoList = new ArrayList<>();
            fileInfoList.addAll(newFileInfoList);
            fileConfig.setFiles(fileInfoList);
            newMeta.setFileConfig(fileConfig);

            // 配置模型参数
            Meta.ModelConfig modelConfig = new Meta.ModelConfig();
            List<Meta.ModelConfig.ModelInfo> modelInfoList = new ArrayList<>();
            modelInfoList.addAll(newModelInfoList);
            modelConfig.setModels(modelInfoList);
            newMeta.setModelConfig(modelConfig);
        }

        // 额外的输出配置
        if(templateMakerOutputConfig != null) {
            // meta.json文件中组内外同名文件的移除和保留处理
            if(templateMakerOutputConfig.isRemoveGroupFileFromRoot()) {
                List<Meta.FileConfig.FileInfo> fileInfoList = newMeta.getFileConfig().getFiles();
                newMeta.getFileConfig().setFiles(TemplateMakerUtils.removeGroupFileFromRoot(fileInfoList));
            }
        }

        // 输出元信息文件
        FileUtil.writeUtf8String(JSONUtil.toJsonPrettyStr(newMeta), metaOutputPath);
        return id;
    }

    /**
     * 处理模型信息列表
     *
     * @param templateMakerModelConfig
     * @return
     */
    private static List<Meta.ModelConfig.ModelInfo> getModelInfoList(TemplateMakerModelConfig templateMakerModelConfig) {
        // 本次新增的模型配置集合
        List<Meta.ModelConfig.ModelInfo> newModelInfoList = new ArrayList<>();

        // 非空校验
        if(templateMakerModelConfig == null) {
            return newModelInfoList;
        }

        // 处理模型信息
        List<TemplateMakerModelConfig.ModelInfoConfig> models = templateMakerModelConfig.getModels();

        // 非空校验
        if(CollUtil.isEmpty(models)) {
            return newModelInfoList;
        }

        // 转换为配置接受的ModelInfo对象
        List<Meta.ModelConfig.ModelInfo> inputModelInfoList = models.stream()
                .map(modelInfoConfig -> {
                    Meta.ModelConfig.ModelInfo modelInfo = new Meta.ModelConfig.ModelInfo();
                    BeanUtil.copyProperties(modelInfoConfig, modelInfo);
                    return modelInfo;
                }).collect(Collectors.toList());

        // 获取模型分组配置
        TemplateMakerModelConfig.ModelGroupConfig modelGroupConfig = templateMakerModelConfig.getModelGroupConfig();
        if(modelGroupConfig != null) {
            // 是模型组
            Meta.ModelConfig.ModelInfo groupModelInfo = new Meta.ModelConfig.ModelInfo();
            // 利用hutool工具包将封装的模型配置属性赋值给模型信息对象
            BeanUtil.copyProperties(templateMakerModelConfig.getModelGroupConfig(), groupModelInfo);

            // 将本次制作传入的所有模型配置信息都放到同一分组下
            groupModelInfo.setModels(inputModelInfoList);
            newModelInfoList.add(groupModelInfo);
        } else {
            // 不是模型组，则将所有模型配置信息都放到本次新增的模型配置集合
            newModelInfoList.addAll(inputModelInfoList);
        }
        return newModelInfoList;
    }

    /**
     * 生成多个模板文件
     *
     * @param templateMakerFileConfig
     * @param templateMakerModelConfig
     * @param sourceRootPath
     * @return
     */
    private static List<Meta.FileConfig.FileInfo> makeFileTemplates(TemplateMakerFileConfig templateMakerFileConfig, TemplateMakerModelConfig templateMakerModelConfig, String sourceRootPath) {
        // 本次新增的文件配置集合
        List<Meta.FileConfig.FileInfo> newFileInfoList = new ArrayList<>();
        // 对templateMakerFileConfig的非空校验
        if(templateMakerFileConfig == null) {
            return newFileInfoList;
        }

        // 二、使用字符串替换，生成模板文件
        // 获取文件信息集合（包括路径，文件过滤配置）
        List<TemplateMakerFileConfig.FileInfoConfig> fileInfoConfigList = templateMakerFileConfig.getFiles();

        // 对fileInfoConfigList的非空校验
        if(CollUtil.isEmpty(fileInfoConfigList)) {
            return newFileInfoList;
        }

        for (TemplateMakerFileConfig.FileInfoConfig fileInfoConfig : fileInfoConfigList) {
            String inputFilePath = fileInfoConfig.getPath();

            // 如果填的是相对路径，要改为绝对路径（因为Hutool工具包根据路径遍历文件中的路径要是绝对路径）
            if(!inputFilePath.startsWith(sourceRootPath)) {
                inputFilePath = sourceRootPath + File.separator + inputFilePath;
            }

            // 获取过滤后的文件对象集合
            List<File> fileList = FileFilter.doFilter(fileInfoConfig.getFilterConfigList(), inputFilePath);
            fileList = fileList.stream()
                    // 如果文件不以.ftl后缀结尾，则保留该文件对象
                    .filter(file -> !file.getAbsolutePath().endsWith(".ftl"))
                    .collect(Collectors.toList());

            // 制作每个文件的模板文件
            for (File file : fileList) {
                Meta.FileConfig.FileInfo fileInfo = makeFileTemplate(templateMakerModelConfig, sourceRootPath, fileInfoConfig, file);
                newFileInfoList.add(fileInfo);
            }
        }

        // 获取文件分组配置
        TemplateMakerFileConfig.FileGroupConfig fileGroupConfig = templateMakerFileConfig.getFileGroupConfig();
        // 如果文件分组配置不为空，则将该此制作模板文件的所有文件配置信息添加到该分组下
        if(fileGroupConfig != null) {
            String groupKey = fileGroupConfig.getGroupKey();
            String groupName = fileGroupConfig.getGroupName();
            String condition = fileGroupConfig.getCondition();

            Meta.FileConfig.FileInfo groupFileInfo = new Meta.FileConfig.FileInfo();
            groupFileInfo.setType(FileTypeEnum.GROUP.getValue());
            groupFileInfo.setGroupKey(groupKey);
            groupFileInfo.setGroupName(groupName);
            groupFileInfo.setCondition(condition);
            // 将该此制作模板文件的所有文件配置信息添加到该分组下
            groupFileInfo.setFiles(newFileInfoList);
            // 分组文件配置信息也在meta.json中的fileConfig的files层级下，
            // 故复用newFileInfoList对象，将该分组配置信息添加到fileConfig的files层级下
            newFileInfoList = new ArrayList<>();
            newFileInfoList.add(groupFileInfo);
        }
        return newFileInfoList;
    }

    /**
     * 制作单个模板文件
     *
     * @param templateMakerModelConfig
     * @param sourceRootPath
     * @param fileInfoConfig
     * @param inputFile
     * @return
     */
    private static Meta.FileConfig.FileInfo makeFileTemplate(TemplateMakerModelConfig templateMakerModelConfig,
                                                             String sourceRootPath,
                                                             TemplateMakerFileConfig.FileInfoConfig fileInfoConfig,
                                                             File inputFile) {
        // 要挖坑的文件绝对路径（用于制作模板）
        // 注意windows系统需要对文件进行转义
        String inputFileAbsolutePath = inputFile.getAbsolutePath().replaceAll("\\\\", "/");
        String outputFileAbsolutePath = inputFileAbsolutePath + ".ftl";

        // 文件输入输出相对路径（用于生成配置）
        String inputFilePath = inputFileAbsolutePath.replace(sourceRootPath + "/", "");
        String outputFilePath = inputFilePath + ".ftl";
        // 使用Hutool工具包读取原始资源文件的所有内容
        String fileContent = null;
        // 模板文件是否存在
        boolean hasTemplateFile = FileUtil.exist(outputFileAbsolutePath);
        if(hasTemplateFile) {
            // 存在，则读取已经制作过的模板文件
            fileContent = FileUtil.readUtf8String(outputFileAbsolutePath);
        } else {
            // 如果模板文件输出路径不存在，则说明是第一次制作，此时读取原始模板文件
            fileContent = FileUtil.readUtf8String(inputFileAbsolutePath);
        }

        // 支持多个模型：对于同一个文件，遍历模型进行多次替换
        TemplateMakerModelConfig.ModelGroupConfig modelGroupConfig = templateMakerModelConfig.getModelGroupConfig();
        // 每次替换后的文件
        String newFileContent = fileContent;
        // 要替换成的内容
        String replacement;
        for (TemplateMakerModelConfig.ModelInfoConfig modelInfoConfig : templateMakerModelConfig.getModels()) {
            if(modelGroupConfig == null) {
                // 不是模型组
                replacement = String.format("${%s}", modelInfoConfig.getFieldName());
            } else {
                // 是模型组
                String groupKey = modelGroupConfig.getGroupKey();
                // 注意挖坑要多挖一层
                replacement = String.format("${%s.%s}",groupKey, modelInfoConfig.getFieldName());
            }
            // 使用Hutool工具包替换原始资源文件中的内容（replaceText为空，则不替换）
            newFileContent = StrUtil.replace(newFileContent, modelInfoConfig.getReplaceText(), replacement);
        }

        // 文件配置信息
        Meta.FileConfig.FileInfo fileInfo = new Meta.FileConfig.FileInfo();
        // 反向输出原因：测试模板制作器中的bug3
        fileInfo.setInputPath(outputFilePath);
        fileInfo.setOutputPath(inputFilePath);
        fileInfo.setCondition(fileInfoConfig.getCondition());
        fileInfo.setType(FileTypeEnum.FILE.getValue());
        // 默认动态生成
        fileInfo.setGenerateType(FileGeneratorTypeEnum.DYNAMIC.getValue());
        // 是否更改了文件内容
        boolean contentEquals = fileContent.equals(newFileContent);
        if(!hasTemplateFile) {
            if(contentEquals) {
                // 如果模板文件不存在并且制作好的文件内容与文件一致，才为静态生成
                fileInfo.setGenerateType(FileGeneratorTypeEnum.STATIC.getValue());
                // 静态生成，输入路径没有 FTL 后缀（原因：测试模板制作器中的bug3）
                fileInfo.setInputPath(inputFilePath);
            } else {
                // 否则为动态生成，输出模板文件
                FileUtil.writeUtf8String(newFileContent, outputFileAbsolutePath);
            }
        } else {
            // 只要模板文件存在，则直接动态生成，并再次输出模板文件（因为可能是再次制作，模板文件内容发生了修改）
            FileUtil.writeUtf8String(newFileContent, outputFileAbsolutePath);
        }

        return fileInfo;
    }

    /**
     * 文件配置去重
     *
     * @param fileInfoList
     * @return
     */
    private static List<Meta.FileConfig.FileInfo> distinctFiles(List<Meta.FileConfig.FileInfo> fileInfoList){
        // 策略：同分组内文件合并，不同分组保留

        // 1.先处理有分组的,以组为单位进行划分
        // 例如：有三个FileInfo对象：fileInfo1{groupKey:a, files:[1,2]}, fileInfo2{groupKey:a, files:[2,3]}, fileInfo3{groupKey:b, files:[4,5]} (files属性中的元素就是fileInfo对象）
        // 将这三个对象放到List集合中，并执行下列代码逻辑后会得到如下Map集合对象
        // map{"a", "fileInfo1{groupKey:a, files:[1,2]}, fileInfo2{groupKey:a, files:[2,3]}", "b", "fileInfo3{groupKey:b, files:[4,5]"}
        Map<String, List<Meta.FileConfig.FileInfo>> groupKeyFileInfoListMap = fileInfoList
                // 将文件信息集合转换成流
                .stream()
                // 根据groupKey不为空或者空值来保留分组文件信息
                .filter(fileInfo -> StrUtil.isNotBlank(fileInfo.getGroupKey()))
                // 根据分组文件信息中的groupKey属性来将相同groupKey（同组）放到同一个Map元素中
                // 将groupKey作为Map对象的键，将同一个groupKey的分组文件信息作为Map对象的值
                .collect(
                        Collectors.groupingBy(Meta.FileConfig.FileInfo::getGroupKey)
                );


        // 2.对于有分组的文件配置，如果有相同的分组，同分组内的文件进行合并（merge），不同分组可同时保留
        // 保存每个分组对应的合并后的对象map
        Map<String, Meta.FileConfig.FileInfo> groupKeyMergedFileInfoMap = new HashMap<>();
        // 遍历Map对象，取到Map对象中的每个元素
        for (Map.Entry<String, List<Meta.FileConfig.FileInfo>> entry : groupKeyFileInfoListMap.entrySet()) {
            // 取到每个元素的值（FileInfo集合）
            List<Meta.FileConfig.FileInfo> tempFileInfoList = entry.getValue();
            List<Meta.FileConfig.FileInfo> newFileInfoList = new ArrayList<>(tempFileInfoList
                    // 将集合转换成流
                    .stream()
                    // 将集合中FileInfo元素中的files（也是FileInfo对象）转成流
                    // 该方法会分别取出上述例子中分组a的files：[1,2,2,3]和分组b中的files：[4,5]
                    .flatMap(fileInfo -> fileInfo.getFiles().stream())
                    // collect()处理的是files流
                    // 该方法会将分组a中的files去重，得到[1,2,3]，而分组b中的files不会有改变
                    .collect(
                            // 使用Collectors中的toMap方法，以原集合中的outputPath属性作为Map的键（原因：测试模板制作器中的bug3）
                            // o -> o表示以原集和中的对象作为值
                            // （exist，replacement） -> replacement 表示如果有键相同的元素，会用新的值替换旧的值
                            Collectors.toMap(Meta.FileConfig.FileInfo::getOutputPath, o -> o, (exist, replacement) -> replacement)
                            // Map对象的values()方法表示获取Map对象中的所有值
                    ).values());

            // 使用新的group配置（主要是追加分组文件配置时，基本信息（例如：groupName）会发生改变，而追加的配置一定会在List集合中的最后一个元素，这是追加配置的逻辑所导致的）
            Meta.FileConfig.FileInfo newFileInfo = CollUtil.getLast(tempFileInfoList);
            // 将该分组中合并后files属性设置到该分组文件配置信息下
            newFileInfo.setFiles(newFileInfoList);
            String groupKey = entry.getKey();
            // 将该分组唯一键和分组文件配置信息添加到Map对象中
            groupKeyMergedFileInfoMap.put(groupKey, newFileInfo);
        }


        // 3.创建新的文件配置列表（结果列表），先将合并后的分组添加到结果列表
        List<Meta.FileConfig.FileInfo> resultList = new ArrayList<>(groupKeyMergedFileInfoMap.values());

        // 4.再将无分组的文件配置列表添加到结果列表
        // 获取所有无分组文件配置信息
        List<Meta.FileConfig.FileInfo> noGroupFileInfoList  = fileInfoList.stream()
                .filter(fileInfo -> StrUtil.isBlank(fileInfo.getGroupKey())).collect(Collectors.toList());
        // 将去重后的无分组文件配置信息添加到结果列表
        resultList.addAll(new ArrayList<>(noGroupFileInfoList.stream()
                .collect(
                    // 使用Collectors中的toMap方法，以原集合中的outputPath属性作为Map的键（原因：测试模板制作器中的bug3）
                    // o -> o表示以原集和中的对象作为值
                    // （exist，replacement） -> replacement 表示如果有键相同的元素，会用新的值替换旧的值
                    Collectors.toMap(Meta.FileConfig.FileInfo::getOutputPath, o -> o , (exist, replacement) -> replacement )
                // Map对象的values()方法表示获取Map对象中的所有值
                ).values()));

        return resultList;
    }

    /**
     * 模型配置去重
     *
     * @param modelInfoList
     * @return
     */
    private static List<Meta.ModelConfig.ModelInfo> distinctModels(List<Meta.ModelConfig.ModelInfo> modelInfoList) {
        // 策略：同分组内模型合并，不同分组保留

        // 1.先处理有分组的,以组为单位进行划分
        // 例如：有三个ModelInfo对象：modelInfo1{groupKey:a, models:[1,2]}, modelInfo2{groupKey:a, models:[2,3]}, modelInfo3{groupKey:b, models:[4,5]} (models属性中的元素就是modelInfo对象）
        // 将这三个对象放到List集合中，并执行下列代码逻辑后会得到如下Map集合对象
        // map{"a", "modelInfo1{groupKey:a, models:[1,2]}, modelInfo2{groupKey:a, models:[2,3]}", "b", "modelInfo3{groupKey:b, models:[4,5]"}
        Map<String, List<Meta.ModelConfig.ModelInfo>> groupKeyModelInfoListMap = modelInfoList
                // 将模型信息集合转换成流
                .stream()
                // 根据groupKey不为空或者空值来保留分组模型信息
                .filter(modelInfo -> StrUtil.isNotBlank(modelInfo.getGroupKey()))
                // 根据分组模型信息中的groupKey属性来将相同groupKey（同组）放到同一个Map元素中
                // 将groupKey作为Map对象的键，将同一个groupKey的分组模型信息作为Map对象的值
                .collect(
                        Collectors.groupingBy(Meta.ModelConfig.ModelInfo::getGroupKey)
                );


        // 2.对于有分组的模型配置，如果有相同的分组，同分组内的模型进行合并（merge），不同分组可同时保留
        // 保存每个分组对应的合并后的对象map
        Map<String, Meta.ModelConfig.ModelInfo> groupKeyMergedModelInfoMap = new HashMap<>();
        // 遍历Map对象，取到Map对象中的每个元素
        for (Map.Entry<String, List<Meta.ModelConfig.ModelInfo>> entry : groupKeyModelInfoListMap.entrySet()) {
            // 取到每个元素的值（ModelInfo集合）
            List<Meta.ModelConfig.ModelInfo> tempModelInfoList = entry.getValue();
            List<Meta.ModelConfig.ModelInfo> newModelInfoList = new ArrayList<>(tempModelInfoList
                    // 将集合转换成流
                    .stream()
                    // 将集合中ModelInfo元素中的models（也是ModelInfo对象）转成流
                    // 该方法会分别取出上述例子中分组a的models：[1,2,2,3]和分组b中的models：[4,5]
                    .flatMap(modelInfo -> modelInfo.getModels().stream())
                    // collect()处理的是models流
                    // 该方法会将分组a中的models去重，得到[1,2,3]，而分组b中的models不会有改变
                    .collect(
                            // 使用Collectors中的toMap方法，以原集合中的fieldName属性作为Map的键
                            // o -> o表示以原集和中的对象作为值
                            // （exist，replacement） -> replacement 表示如果有键相同的元素，会用新的值替换旧的值
                            Collectors.toMap(Meta.ModelConfig.ModelInfo::getFieldName, o -> o, (exist, replacement) -> replacement)
                            // Map对象的values()方法表示获取Map对象中的所有值
                    ).values());

            // 使用新的group配置（主要是追加分组模型配置时，基本信息（例如：groupName）会发生改变，而追加的配置一定会在List集合中的最后一个元素，这是追加配置的逻辑所导致的）
            Meta.ModelConfig.ModelInfo newModelInfo = CollUtil.getLast(tempModelInfoList);
            // 将该分组中合并后models属性设置到该分组模型配置信息下
            newModelInfo.setModels(newModelInfoList);
            String groupKey = entry.getKey();
            // 将该分组唯一键和分组模型配置信息添加到Map对象中
            groupKeyMergedModelInfoMap.put(groupKey, newModelInfo);
        }


        // 3.创建新的模型配置列表（结果列表），先将合并后的分组添加到结果列表
        List<Meta.ModelConfig.ModelInfo> resultList = new ArrayList<>(groupKeyMergedModelInfoMap.values());

        // 4.再将无分组的模型配置列表添加到结果列表
        // 获取所有无分组模型配置信息
        List<Meta.ModelConfig.ModelInfo> noGroupModelInfoList  = modelInfoList.stream()
                .filter(modelInfo -> StrUtil.isBlank(modelInfo.getGroupKey())).collect(Collectors.toList());
        // 将去重后的无分组模型配置信息添加到结果列表
        resultList.addAll(new ArrayList<>(noGroupModelInfoList.stream()
                .collect(
                        // 使用Collectors中的toMap方法，以原集合中的fieldName属性作为Map的键
                        // o -> o表示以原集和中的对象作为值
                        // （exist，replacement） -> replacement 表示如果有键相同的元素，会用新的值替换旧的值
                        Collectors.toMap(Meta.ModelConfig.ModelInfo::getFieldName, o -> o , (exist, replacement) -> replacement )
                        // Map对象的values()方法表示获取Map对象中的所有值
                ).values()));

        return resultList;
    }
}
